package r25.tools;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>
 * LoggerHelper class.
 * </p>
 * 
 * A helper based on {@link org.slf4j.Logger}<br />
 * It avoids to declare the calling class name and get a string with the
 * exception stack trace
 * 
 * @author <a href="mailto:philippe.bajoit@ulg.ac.be">PB</a>
 * @version 0.0.1
 */
public class LoggerHelper {
	private static final int MB = 1024 * 1024;

	/**
	 * <p>
	 * getLogger
	 * </p>
	 * 
	 * @return a {@link org.slf4j.Logger} object aware of the calling class
	 *         name.
	 */
	public static Logger getLogger() {
		final Throwable t = new Throwable();
		t.fillInStackTrace();
		return LoggerFactory.getLogger(t.getStackTrace()[1].getClassName());
	}

	/**
	 * <p>
	 * getStackTrace
	 * </p>
	 * 
	 * @return a {@link java.lang.String} object containing the stack trace of
	 *         the exception, useful for logging in a file.
	 * @param e
	 *            a {@link java.lang.Throwable} object.
	 */
	public static String getStackTrace(Throwable e) {
		StringWriter outError = new StringWriter();
		PrintWriter errorWriter = new PrintWriter(outError);
		e.printStackTrace(errorWriter);
		return outError.toString();
	}

	/**
	 * <p>
	 * toString
	 * </p>
	 * 
	 * @param is
	 *            a {@link java.io.InputStream} object.
	 * @return a {@link java.lang.String} object.
	 * @throws java.lang.Exception
	 *             if any.
	 */
	public static String toString(InputStream is) throws Exception {
		if (is == null)
			return "";

		final char[] buffer = new char[0x10000];
		StringBuilder out = new StringBuilder();
		Reader in = new InputStreamReader(is);

		int read;
		do {
			read = in.read(buffer, 0, buffer.length);
			if (read > 0) {
				out.append(buffer, 0, read);
			}
		} while (read >= 0);

		return out.toString();
	}

	/**
	 * <p>
	 * toStringMap
	 * </p>
	 * 
	 * @param map
	 *            a {@link java.util.Map} object.
	 * @return a {@link java.lang.String} object.
	 */
	public static String toStringMap(Map<?, ?> map) {
		return toStringMap(map, null);
	}

	/**
	 * <p>
	 * toStringMap
	 * </p>
	 * 
	 * @param map
	 *            a {@link java.util.Map} object.
	 * @param excludeKeys
	 *            a {@link java.lang.String} object.
	 * @return a {@link java.lang.String} object.
	 */
	public static String toStringMap(Map<?, ?> map, String excludeKeys) {
		List<String> nokeys = new ArrayList<String>();

		if (excludeKeys != null) {
			String[] keys = excludeKeys.split(",");
			for (String k : keys) {
				nokeys.add(k);
			}
		}
		return toStringMap(map, nokeys, 0);
	}

	/**
	 * <p>
	 * toStringMap
	 * </p>
	 * 
	 * @param map
	 *            a {@link java.util.Map} object.
	 * @param nokeys
	 *            a {@link java.util.List} object.
	 * @param depth
	 *            a int.
	 * @return a {@link java.lang.String} object.
	 */
	public static String toStringMap(Map<?, ?> map, List<String> nokeys,
			int depth) {
		if (map == null)
			return NULL;

		StringBuilder result = new StringBuilder(START_MAP_CHAR);
		boolean isFirst = true;
		for (Object key : map.keySet()) {
			if (nokeys.contains(key)) {
				continue;
			}

			if (!isFirst) {
				result.append(MAP_SEPARATOR);
			} else {
				isFirst = false;
			}

			for (int i = 0; i < depth; i++) {
				result.append(MAP_TAB);
			}
			result.append(MAP_PREFIX);
			result.append(toString(key));
			result.append(MAP_EQUALS);
			Object v = map.get(key);
			if (v instanceof Map<?, ?>) {
				result.append(toStringMap((Map<?, ?>) v, nokeys, 1 + depth));
			} else {
				result.append(toString(v));
			}
		}
		result.append(END_MAP_CHAR);

		return result.toString();
	}

	/**
	 * Convenience method for producing a simple textual representation of an
	 * array.
	 * 
	 * <p>
	 * The format of the returned <code>String</code> is the same as
	 * <code>AbstractCollection.toString</code>:
	 * <ul>
	 * <li>non-empty array: [blah, blah]
	 * <li>empty array: []
	 * <li>null array: null
	 * </ul>
	 * </p>
	 * 
	 * <code>aArray</code> is a possibly-null array whose elements are
	 * primitives or objects; arrays of arrays are also valid, in which case
	 * <code>aArray</code> is rendered in a nested, recursive fashion.
	 * 
	 * @return a {@link java.lang.String} object containing contents of the
	 *         <code>aArray</code>.
	 * @param aArray
	 *            a {@link java.lang.Object} object.
	 */
	public static String toString(Object aArray) {
		return toString(aArray, SEPARATOR);
	}

	public static String toString(Object aArray, String separator) {
		if (aArray == null)
			return NULL;

		if (isObjectArray(aArray))
			return aArray.toString();

		StringBuilder result = new StringBuilder(START_CHAR);
		int length = Array.getLength(aArray);
		for (int idx = 0; idx < length; ++idx) {
			Object item = Array.get(aArray, idx);
			if (isNonNullArray(item)) {
				// recursive call
				result.append(toString(item, separator));
			} else {
				result.append(item);
			}
			if (!isLastItem(idx, length)) {
				result.append(separator);
			}
		}
		result.append(END_CHAR);
		result.append(COUNT_CHAR);
		result.append(length);
		return result.toString();
	}

	/**
	 * <p>
	 * getMemoryStatistics
	 * </p>
	 * 
	 * @return a {@link java.lang.String} object.
	 */
	public static String getMemoryStatistics() {
		StringBuffer rc = new StringBuffer();

		Runtime runtime = Runtime.getRuntime();
		rc.append("-- Heap utilization statistics [MB] ------");
		rc.append("\nUsed Memory:  ");
		rc.append((runtime.totalMemory() - runtime.freeMemory()) / MB);
		rc.append("\nFree Memory:  ");
		rc.append(runtime.freeMemory() / MB);
		rc.append("\nTotal Memory: ");
		rc.append(runtime.totalMemory() / MB);
		rc.append("/");
		rc.append(runtime.maxMemory() / MB);
		rc.append("\n------------------------------------------");

		return rc.toString();
	}

	/**
	 * <p>
	 * getMemoryShortStatistics
	 * </p>
	 * 
	 * @return a {@link java.lang.String} object.
	 */
	public static String getMemoryShortStatistics() {
		StringBuffer rc = new StringBuffer();

		Runtime runtime = Runtime.getRuntime();
		rc.append("Heap stats [MB] used ");
		rc.append((runtime.totalMemory() - runtime.freeMemory()) / MB);
		rc.append(" free ");
		rc.append(runtime.freeMemory() / MB);
		rc.append(" Total: ");
		rc.append(runtime.totalMemory() / MB);
		rc.append("/");
		rc.append(runtime.maxMemory() / MB);

		return rc.toString();
	}

	/**
	 * <p>
	 * getTimerStatistics
	 * </p>
	 * 
	 * @param startTimer
	 *            a long.
	 * @return a {@link java.lang.String} object.
	 */
	public static String getTimerStatistics(long startTimer) {
		return getTimerStatistics(startTimer, System.currentTimeMillis());
	}

	/**
	 * <p>
	 * getTimerStatistics
	 * </p>
	 * 
	 * @param startTimer
	 *            a long.
	 * @param currentTimeMillis
	 *            a long.
	 * @return a {@link java.lang.String} object.
	 */
	public static String getTimerStatistics(long startTimer,
			long currentTimeMillis) {
		StringBuffer rc = new StringBuffer(
				"-- Timer statistics ----------------------");
		rc.append("\n");
		rc.append("Total time: ");
		rc.append(new Float(currentTimeMillis - startTimer) / 1000);
		rc.append("s\n");
		rc.append("Finished at: ");
		rc.append(new Date());
		rc.append("\n------------------------------------------");

		return rc.toString();
	}

	// PRIVATE //
	private static final String COUNT_CHAR = "#";
	private static final String START_CHAR = "[";
	private static final String END_CHAR = "]";
	private static final String SEPARATOR = ", ";

	private static final String NULL = "null";

	private static final String START_MAP_CHAR = "{\n";
	private static final Object END_MAP_CHAR = "\n}";
	private static final String MAP_SEPARATOR = ",\n";
	private static final String MAP_PREFIX = " * ";
	private static final String MAP_EQUALS = "=";
	private static final String MAP_TAB = "  ";

	private static boolean isObjectArray(Object aArray) {
		return !aArray.getClass().isArray();
	}

	private static boolean isNonNullArray(Object aItem) {
		return aItem != null && aItem.getClass().isArray();
	}

	private static boolean isLastItem(int aIdx, int aLength) {
		return aIdx == aLength - 1;
	}
}
